package com.small.common.manager.redis.impl;

import com.alibaba.fastjson.JSON;
import com.small.common.manager.redis.RedisClient;
import com.small.common.util.FastJsonUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Description Jedis客户端实现类
 * @Author DingYou
 * @Date 2018-07-10 11:43
 * @Version 1.0
 */
@Service
public class RedisClientImpl implements RedisClient {
    /**
     * 默认scan命令中的count参数
     * 此值设置的越大,对redis服务的影响越大,设置的越小,scan命令完成的耗时越长
     */
    private static final int DEFAULT_SCAN_COUNT = 1000;

    private final JedisPool jedisPool;

    @Autowired
    public RedisClientImpl(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 执行function中的逻辑
     *
     * @param function
     * @param <T>
     * @return
     */
    @Override
    public <T> T execute(Function<Jedis, T> function) {
        try (Jedis jedis = jedisPool.getResource()) {
            return function.apply(jedis);
        }
    }

    /**
     * 执行consumer中的逻辑
     * @param consumer
     */
    @Override
    public void consume(Consumer<Jedis> consumer) {
        try(Jedis jedis = jedisPool.getResource()){
            consumer.accept(jedis);
        }
    }

    @Override
    public String set(final String key, final Object value) {
        return execute(jedis -> jedis.set(key, getStringValue(value)));
    }

    /**
     * set值,同时设置过期时间,单位秒
     *
     * @param key
     * @param value
     * @param time
     * @return
     */
    @Override
    public String set(final String key, final Object value, final int time) {
        return execute(jedis -> jedis.setex(key, time, getStringValue(value)));
    }

    /**
     * 完全自主的set方法
     *
     * @param key
     * @param value
     * @param nxxx  nxxx NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
     * @param expx  expx EX|PX, expire time units: EX = seconds; PX = milliseconds
     * @param time  time expire time in the units of {@param #expx}
     * @return
     */
    @Override
    public String set(final String key, final Object value, final String nxxx, final String expx,
                      final int time) {
        return execute(jedis -> jedis.set(key, getStringValue(value), nxxx, expx, time));
    }

    /**
     * 获取String型value
     *
     * @param key
     * @return
     */
    @Override
    public String get(String key) {
        return execute(jedis -> jedis.get(key));
    }

    /**
     * 获取<T>型value
     *
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> T get(String key, Class<T> clazz) {
        String stringValue = get(key);
        if (stringValue == null) {
            return null;
        }
        return JSON.parseObject(stringValue, clazz);
    }

    /**
     * 获取元素类型为<T>的List类型value
     *
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> List<T> getList(String key, Class<T> clazz) {
        String stringValue = get(key);
        if (stringValue == null) {
            return null;
        }
        return JSON.parseArray(stringValue, clazz);
    }

    /**
     * 获取Map型value
     *
     * @param key
     * @param keyType
     * @param valueType
     * @return
     */
    @Override
    public <K, V> Map<K, V> getMap(String key, Class<K> keyType, Class<V> valueType) {
        String stringValue = get(key);
        if (stringValue == null) {
            return null;
        }
        return FastJsonUtils.parseToMap(stringValue, keyType, valueType);
    }

    /**
     * 设置过期时间,单位毫秒
     *
     * @param key
     * @param milliseconds
     * @return
     */
    @Override
    public Long pexpire(final String key, final long milliseconds) {
        return execute(jedis -> jedis.pexpire(key, milliseconds));
    }

    /**
     * 设置指定单位的过期时间,最小到毫秒
     *
     * @param key
     * @param times
     * @param timeUnit
     * @return
     */
    @Override
    public Long pexpire(final String key, final long times, final TimeUnit timeUnit) {
        return execute(jedis -> jedis.pexpire(key, timeUnit.toMillis(times)));
    }

    /**
     * 设置过期时间,单位秒
     *
     * @param key
     * @param seconds
     * @return
     */
    @Override
    public Long expire(final String key, final int seconds) {
        return execute(jedis -> jedis.expire(key, seconds));
    }

    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    @Override
    public Boolean exists(final String key) {
        return execute(jedis -> jedis.exists(key));
    }

    /**
     * 设置hash型数据
     *
     * @param key
     * @param field
     * @param value
     * @return
     */
    @Override
    public Long hset(final String key, final String field, final Object value) {
        return execute(jedis -> jedis.hset(key, field, getStringValue(value)));
    }

    /**
     * 删除hash中的指定字段
     *
     * @param key
     * @param fields
     * @return
     */
    @Override
    public Long hdel(String key, String... fields) {
        return execute(jedis -> jedis.hdel(key, fields));
    }

    /**
     * 获取hash中的String类型的value
     *
     * @param key
     * @param field
     * @return
     */
    @Override
    public String hget(final String key, final String field) {
        return execute(jedis -> jedis.hget(key, field));
    }

    /**
     * 获取hash中T型的value
     *
     * @param key
     * @param field
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> T hget(final String key, final String field, Class<T> clazz) {
        String stringValue = execute(jedis -> jedis.hget(key, field));
        if (stringValue == null) {
            return null;
        }
        return JSON.parseObject(stringValue, clazz);
    }

    /**
     * hash中指定字段自增指定值
     *
     * @param key
     * @param field
     * @param value
     * @return
     */
    @Override
    public Long hincrBy(String key, String field, long value) {
        return execute(jedis -> jedis.hincrBy(key, field, value));
    }

    /**
     * 获取hash中元素为T型的List型value
     *
     * @param key
     * @param field
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> List<T> hgetList(final String key, final String field, Class<T> clazz) {
        String stringValue = execute(jedis -> jedis.hget(key, field));
        if (stringValue == null) {
            return null;
        }
        return JSON.parseArray(stringValue, clazz);
    }

    /**
     * 删除key
     *
     * @param keys
     */
    @Override
    public Long del(String... keys) {
        return execute(jedis -> jedis.del(keys));
    }

    /**
     * 添加元素进set
     *
     * @param key
     * @param members
     * @return
     */
    @Override
    public Long sadd(String key, Object... members) {
        String[] strArr = Arrays.stream(members).map(this::getStringValue).toArray(String[]::new);
        return execute(jedis -> jedis.sadd(key, strArr));
    }

    /**
     * 从set中弹出一个元素
     *
     * @param key
     * @return
     */
    @Override
    public String spop(String key) {
        return execute(jedis -> jedis.spop(key));
    }

    /**
     * 从set中弹出一个元素并转成目标类型
     *
     * @param key
     * @param clazz
     * @return
     */
    @Override
    public <T> T spop(String key, Class<T> clazz) {
        String spop = spop(key);
        return spop == null ? null : JSON.parseObject(spop, clazz);
    }

    /**
     * 从set中弹出一批元素
     *
     * @param key
     * @param count
     * @return
     */
    @Override
    public Set<String> spop(String key, long count) {
        return execute(jedis -> jedis.spop(key, count));
    }

    /**
     * 从set中弹出一批元素
     *
     * @param key
     * @param count
     * @param clazz
     * @return
     */
    @Override
    public <T> Set<T> spop(String key, long count, Class<T> clazz) {
        Set<String> spop = spop(key, count);
        if (Objects.isNull(spop)) {
            return null;
        }
        return spop.stream().map(s -> JSON.parseObject(s, clazz)).collect(Collectors.toSet());
    }

    /**
     * 获取set中所有的元素
     * @param key
     * @return members or an empty set
     */
    @Override
    public Set<String> smembers(String key) {
        /*
        jedis不会返回null
         */
        return execute(jedis -> jedis.smembers(key));
    }

    /**
     * 获取set中所有的成员, 并转成目标类型
     * @param key
     * @param clazz
     * @return
     */
    @Override
    public <T> Set<T> smembers(String key, Class<T> clazz) {
        Set<String> smembers = smembers(key);
        if(smembers.isEmpty()){
            return null;
        }
        return smembers.stream().map(s -> JSON.parseObject(s, clazz)).collect(Collectors.toSet());
    }

    /**
     * 判断value是否是set的成员
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean sismember(String key, Object value) {
        /*
        这里jedis方法的返回值类型是Boolean,但是事实上它返回的是client.getIntegerReply() == 1;
        这个表达式的结果,不可能为null,所以直接返回基本类型,以减少调用者不必要的null值校验
         */
        return execute(jedis -> jedis.sismember(key, getStringValue(value)));
    }

    /**
     * @param key
     * @Author: yinhaijun
     * @Description: 查询hash下所有的key-value
     * @Date: 2018/8/31 15:22
     * @param: s
     * @return:
     * @version: 1.2
     */
    @Override
    public Map<String, String> hgetAll(String key) {
        return execute(jedis -> jedis.hgetAll(key));
    }

    /**
     * 获取list最后一个元素, 获取不到就阻塞
     *
     * @param key
     * @return
     */
    @Override
    public String brpop(String key) {
        List<String> stringList = execute(jedis -> jedis.brpop(0, key));
        //这里是一定会取到偶数个String, 奇数表示key, 偶数表示value
        return stringList.get(1);
    }

    /**
     * 获取list最后一个元素, 获取不到就阻塞
     *
     * @param key
     * @param clazz
     * @return
     */
    @Override
    public <T> T brpop(String key, Class<T> clazz) {
        String brpop = brpop(key);
        return JSON.parseObject(brpop, clazz);
    }

    /**
     * 将value从左侧插入list
     *
     * @param key
     * @param values
     * @return
     */
    @Override
    public Long lpush(String key, Object... values) {
        if (Objects.isNull(values)) {
            return 0L;
        }
        final String[] stringValues = Arrays.stream(values).map(this::getStringValue).toArray(String[]::new);
        return execute(jedis -> jedis.lpush(key, stringValues));
    }

    /**
     * 循环扫描redis中符合pattern的key,用来替代keys命令,谨慎调用
     * @param pattern
     * @return
     */
    @Override
    public List<String> scan(String pattern) {
        List<String> result = new ArrayList<>();
        String cursor = ScanParams.SCAN_POINTER_START;

        ScanParams scanParams = new ScanParams();
        scanParams.count(DEFAULT_SCAN_COUNT);
        scanParams.match(pattern);

        try (Jedis jedis = jedisPool.getResource()) {
            do {
                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
                result.addAll(scanResult.getResult());
                cursor = scanResult.getStringCursor();
            } while (!ScanParams.SCAN_POINTER_START.equals(cursor));
        }
        return result;
    }

    /**
     * 循环扫描redis中的指定条件的key,用来替代keys命令,谨慎调用
     * @param cursor
     * @param scanParams
     * @return
     */
    @Override
    public ScanResult<String> scan(String cursor, ScanParams scanParams){
        return execute(jedis -> jedis.scan(cursor, scanParams));
    }

    /**
     * 将Object转成json
     *
     * @param value
     * @return
     */
    private String getStringValue(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            return (String) value;
        }
        return JSON.toJSONString(value);
    }
}
